﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;

namespace GShare1._0.Client
{
    public delegate void MainThreadInvoker();
    public delegate void MessageBoxInvoker(string msg);
    public class ClientConnection
    {
        //private variables
        private IPAddress _serverIp;
        private int _port;
        private AutoResetEvent _autoReset;
        private Download download = new Download();
        private bool _connected;
        private string _username;
        private MainThreadInvoker _invoker;
        private MessageBoxInvoker mboxInvoker;
        private byte[] buffer;
        private Socket _clientSocket;
        private FileManager fileManager;

        #region props

        public Download Download
        {
            get { return download; }
            set { download = value; }
        }
        public FileManager FileManager
        {
            get { return fileManager; }
            set { fileManager = value; }
        }
        public byte[] Buffer
        {
            get { return buffer; }
            set { buffer = value; }
        }

        public Socket Socket
        {
            get { return _clientSocket; }
            set { _clientSocket = value; }
        }

        public string Username
        {
            get { return _username; }
            set { _username = value; }
        }

        public MessageBoxInvoker MessageInvoker
        {
            get { return mboxInvoker; }
            set { mboxInvoker = value; }
        } 

        public MainThreadInvoker Invoker
        {
            get { return _invoker; }
            set { _invoker = value; }
        }

        public bool Connected
        {
            get { return _connected; }
            set { _connected = value; }
        }
        #endregion

        public ClientConnection(IPAddress ip, int port,AutoResetEvent autoReset,string username)
        {
            _username = username;
            _port = port;
            _serverIp = ip;
            _connected = false;
            this._autoReset = autoReset;
            fileManager = new FileManager();
        }


        /// <summary>
        /// Tries to connect to the socket using BeginConnect Asynchronusly
        /// </summary>
        internal void Connect()
        {
            try
            {
                //CreateFolders
                fileManager.CheckContainingFolder();

                _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _clientSocket.BeginConnect(_serverIp, _port, new AsyncCallback(ConnectCallback), null);

            }
            catch (Exception)
            {

                throw;
            }
        }

        /// <summary>
        /// When the Client Connects to the socket Successfuly this is called (Callback for BeginConnect)
        /// Sends Buffer with login info to server
        /// </summary>
        /// <param name="AR">IAsyncResult from the BeginConnect</param>
        private void ConnectCallback(IAsyncResult AR)
        {
            try
            {
                buffer = Encoding.ASCII.GetBytes(_username);
                //send login info to server
                    
                if (_clientSocket.Connected)
                {
                    _clientSocket.Send(buffer);
                    //Buffer = Encoding.ASCII.GetBytes("Lol");
                    //_clientSocket.Send(Buffer);
                    _clientSocket.EndConnect(AR);
                    _connected = true;

                    buffer = new byte[_clientSocket.ReceiveBufferSize];
                    _clientSocket.BeginReceive(buffer, 0, _clientSocket.ReceiveBufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
                    _autoReset.Set();
                }
                else
                {
                    _connected = false;
                    _invoker.BeginInvoke(null, null);
                    return;
                }

            }
            catch (Exception ex)
            {

                MessageBox.Show(ex.Message);
            }
        }

        internal void ReceiveCallback(IAsyncResult AR)
        {
            try
            {
                
                //INSERT SOME HANDELING FOR SERVER GOING OFFLINE ATM ITS CAUGHT HERE
                if (_clientSocket.Connected)
                {
                    int received = _clientSocket.EndReceive(AR);
                    //Check if still connected
                    if (received != 0)
                    {
                        Array.Resize(ref buffer, received);

                        string msg = Encoding.ASCII.GetString(buffer);

                        if (msg == "COMMANDERROR")
                        {
                            //End the active download
                            if (download.Alive)
                            {
                                download.ServerReceiving = false;
                                mboxInvoker.BeginInvoke("Upload Stopped",null,null);
                            }
                        }
                        else
                        {
                            MessageReceiver.ReadMessage(msg, mboxInvoker);
                        }
                        
                        //Restart Receive Async Loop
                        buffer = new byte[_clientSocket.ReceiveBufferSize];
                        //common problem where server goes off line during normal functioning
                        try
                        {
                            _clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
                        }
                        catch (Exception)
                        {
                        }
                    }
                    else
                    {
                        //We have Disconnected from the server now
                        //Display Message
                        //Close Form
                        //Set Connected = false;
                        //End Loop
                        MessageBox.Show("Disconnected From Server");
                        Invoker.BeginInvoke(null, null);
                        _connected = false;
                        return;
                    }
                    
                }
                else
                {
                }

            }
            catch (Exception ex)
            {

            }

        }

        internal void Disconnect()
        {
            try
            {
                _clientSocket.Shutdown(SocketShutdown.Both);
                _clientSocket.Dispose();
                _clientSocket.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        /// <summary>
        /// sends message to the server
        /// </summary>
        /// <param name="messageType">Type of Message that the server will use to determin what to do with the body</param>
        /// <param name="message">the body of the message if needed</param>
        internal void SendMessage(string messageType,string message = null)
        {
            try
            {
                
                
                if (!string.IsNullOrEmpty(messageType))
                {
                    byte[] sendBuffer = Encoding.ASCII.GetBytes(messageType);
                    _clientSocket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, new AsyncCallback(SendCallback), null);
                    _autoReset.WaitOne();
                }
                if (message != null)
                {
                    byte[] sendBuffer = Encoding.ASCII.GetBytes(message);
                    _clientSocket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, new AsyncCallback(SendCallback), null);
                    _autoReset.WaitOne();
                }
                
                
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void SendCallback(IAsyncResult ar)
        {
            _autoReset.Set();
        }

        internal void UploadFile()
        {
            
            _autoReset = new AutoResetEvent(false);
            SendMessage("SENDFILE");
            download = new Client.Download();
            download.ClientConnection = this;
            download.StartConnection(_serverIp);
            download.ThreadComplete.WaitOne();
            //NEED TO ADD WAIT HERE FOR THE UPLOAD TO FINISH
            //upload file but if upload stopped send message to server that uplaod is cancled
            if (!download.Alive)
            {
                //clear download
                download = new Client.Download();
                SendMessage("ENDUPLOAD");
            }
            //download.ThreadComplete.WaitOne();
        }
    }
}
